import os.path
import signal
from concurrent.futures import ThreadPoolExecutor
import socket
import logging

# 日志记录器
logger: logging.Logger | None = None


def logger_init():
    global logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)

    # 格式器
    formatter = logging.Formatter("%(levelname)s|%(asctime)s:%(message)s")
    # debug处理器
    if not os.path.exists('log'):
        os.mkdir('log')
    debug_handler = logging.FileHandler("log/server.log", encoding="utf-8")
    debug_handler.setLevel(logging.DEBUG)
    debug_handler.setFormatter(formatter)
    logger.addHandler(debug_handler)
    # 控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)


logger_init()


class Client:
    def __init__(self, conn: socket.socket, address: tuple[str, int], name: str = None, secret: bool = False):
        self.conn = conn
        self.address = address
        self.name = name
        self.secret = secret


class Server:
    def __init__(self, port, host='0.0.0.0', backlog=None, thread=5, timeout=3):
        """
        基础服务器, 构造方法会根据host和port绑定到相应的主机和端口, 并启动监听, backlog设置允许排队的客户端数量
        start方法会接受客户端连接, 交由线程池处理, 线程数由thread属性决定, 默认为5
        由timeout决定检查运行标志的间隔时长, 默认为3, 即每3秒检查一次, 发出终止信号后服务器将在3秒内关闭
        signal_handler方法用于实现服务器的具体功能, 由子类实现
        :param port: 服务器绑定的端口
        :param host: 服务器绑定的主机, 默认为0.0.0.0
        :param backlog: 监听允许排队的客户端数量, 默认为None, 由python自动分配
        :param thread: 线程数, 允许同时连接的客户端数量, 默认为5
        :param timeout: 超时时间, 检查运行标志的间隔时长, 相当于关闭服务器需要等待的时长, 默认为3秒
        """
        if logger is None:
            logger_init()
        logger.debug("初始化服务器")
        self.host = host
        self.port = port
        self.backlog = backlog
        self.thread = thread
        # 处理终止信号
        signal.signal(signal.SIGINT, self.signal_handler)
        signal.signal(signal.SIGTERM, self.signal_handler)
        logger.debug(f"已设置检查的终止信号")
        self.listener = socket.create_server((self.host, self.port), backlog=self.backlog)
        logger.info("服务器正在监听...")
        self.client_list = []
        self.running = True
        self.timeout = timeout

    def signal_handler(self, sig, frame):
        logger.debug("接收到终止信号，正在停止服务器...")
        self.running = False

    def start(self):
        """
        启动服务器, 接受客户端连接, 交由线程池处理, 线程数由thread属性决定, 默认为5
        由Server实例的timeout决定检查运行标志的间隔时长, 默认为3, 即每3秒检查一次, 发出终止信号后服务器将在3秒内关闭
        """
        with (ThreadPoolExecutor(self.thread) as tpe):
            try:
                self.listener.settimeout(self.timeout)
                while self.running:
                    conn: socket.socket | None = None
                    address: tuple[str, int] | None = None
                    try:
                        conn, address = self.listener.accept()
                    except TimeoutError:
                        continue
                    logger.info(f"连接来自: {address}")
                    client = Client(conn, address)
                    self.client_list.append(client)
                    logger.debug(f"{address} 已存入服务器的客户端列表")
                    tpe.submit(self.handle_client, client)
            except KeyboardInterrupt:
                logger.info("服务器停止")
            finally:
                self.listener.close()
        logger.info("服务器已关闭")

    def handle_client(self, client):
        """客户端的处理方式需要由子类实现"""
        raise NotImplementedError()


class EchoServer(Server):
    def __init__(self, port, host='0.0.0.0', backlog=None, thread=5, timeout=3):
        """
        TCP回声服务器, 接受客户端连接, 并将客户端发送的信息返回给客户端
        构造方法会根据host和port绑定到相应的主机和端口, 并启动监听, backlog设置允许排队的客户端数量
        thread决定能同时连接的客户端数量, 默认为5
        timeout决定检查运行标志的间隔时长, 默认为3, 即每3秒检查一次, 发出终止信号后服务器将在3秒内关闭
        :param port: 服务器绑定的端口
        :param host: 服务器绑定的主机, 默认为0.0.0.0
        :param backlog: 监听允许排队的客户端数量, 默认为None, 由python自动分配
        :param thread: 线程数, 允许同时连接的客户端数量, 默认为5
        :param timeout: 超时时间, 检查运行标志的间隔时长, 相当于关闭服务器需要等待的时长, 默认为3秒
        """
        super().__init__(port, host, backlog, thread, timeout)
        self.msg_logger = logging.getLogger('msg')
        self.msg_logger.setLevel(logging.INFO)
        fmt = logging.Formatter("%(address)s:%(port)d [%(asctime)s] %(message)s", datefmt="%y%m%d %H:%M:%S")
        msg_handler = logging.FileHandler("./log/msg.log")
        msg_handler.setLevel(logging.INFO)
        msg_handler.setFormatter(fmt)
        self.msg_logger.addHandler(msg_handler)

    def handle_client(self, client: Client):
        """
        服务器的业务方法, 接收客户端发送的信息并返回
        :param client: 客户端对象, 需要客户端的连接套接字和客户端地址
        """
        try:
            logger.debug(f"处理客户端 {client.address}")
            client.conn.settimeout(60)
            logger.debug("连接超时时间设置为60秒")
            while True:
                data = client.conn.recv(1024)
                if not data:
                    break
                try:
                    msg = data.decode()
                    if msg.lower() == 'exit':
                        break
                    client.conn.sendall(data)
                    self.msg_logger.info(f"{msg}", extra={'address': client.address[0], 'port': client.address[1]})
                    logger.debug(f"收到消息: {msg}, 发送回应.")
                except UnicodeDecodeError as e:
                    client.conn.sendall(data)
                    self.msg_logger.info(f"{e.args[1]}", extra={'address': client.address[0], 'port': client.address[1]})
                    logger.debug(f"收到无法解码的字节串: {e.args[1]}, 发送回应.")
        except TimeoutError as e:
            logger.debug(f"接收客户端 {client.address} 信息超时.")
        except Exception as e:
            logger.error(f"处理客户端 {client.address} 时发生错误: {e}")
        finally:
            client.conn.close()
            logger.info(f"客户端 {client.address} 已断开连接.")
            self.client_list.remove(client)
            logger.debug(f"已从服务器的客户端列表删除客户端 {client.address}")


if __name__ == '__main__':
    es = EchoServer(6666)
    es.start()
